<template>
	<a-row :gutter="10" :body-style="{ 'padding-bottom': '0px' }" class="mb-2">
		<a-col :xs="24" :sm="24" :md="24" :lg="5" :xl="5">
			<a-card>
				<div style="width: 160px; height:400px;overflow-x: scroll">
					<a-tree ref="tree" :tree-data="config.fileMenu"
							:fieldNames="config.defaultProps"
							:loadData="loadNode"
							@select="handleNodeClick">

						<template #title="{ key: treeKey, label,isLeaf,path ,dir}">
							<a-dropdown :trigger="['contextmenu']">
								<span>{{ label }}</span>
								<template #overlay>
									<a-menu
										v-if="!props.readonly"
										@click="({ key: menuKey }) => onContextMenuClick(treeKey, menuKey,label,isLeaf,path,dir)">
										<a-menu-item key="1" v-if="dir">新增子文件夹</a-menu-item>
										<a-menu-item key="2" v-if="dir">新增子文件</a-menu-item>
										<a-menu-item key="3">重命名</a-menu-item>
										<a-menu-item key="4" v-if="dir">删除文件夹</a-menu-item>
										<a-menu-item key="5" v-if="!dir">删除</a-menu-item>

									</a-menu>
								</template>
							</a-dropdown>
						</template>

					</a-tree>

					<a-modal v-model:open="reNameDiag.visiable" width="300px">
						<a-form :model="reNameDiag">
							<a-form-item label="名称">
								<a-input v-model:value="reNameDiag.name"></a-input>
							</a-form-item>
						</a-form>
						<template #footer>
							<a-button @click="reNameDiag.visiable = false">取 消</a-button>
							<a-button type="primary" @click="saveFileByType">确 定</a-button>
						</template>
					</a-modal>
				</div>
			</a-card>
		</a-col>


		<a-col :xs="24" :sm="24" :md="24" :lg="19" :xl="19">
			<a-card>
				<div ref="compFileEditor" style="height: 400px; text-align: left"></div>
			</a-card>
		</a-col>
	</a-row>
</template>

<script setup>
import {h, toRaw} from 'vue'
import {message} from 'ant-design-vue'
import JSZip from 'jszip'
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import * as monaco from 'monaco-editor'
import {customLanguageConfiguration, customTheme, customTokenProvider} from '@/views/comp/MonacoJavaCustom.js'
import groovyBeautify from 'groovy-beautify'
// ES modules
import {SocketD} from '@noear/socket.d'
import processCompApi from '@/api/comp/processCompApi'
import processCompDebuggerApi from '@/api/comp/processCompDebuggerApi'
import {nextTick} from "@/utils";

// @ts-ignore
self.MonacoEnvironment = {
	getWorker(_, label) {
		if (label === 'json') {
			return new jsonWorker()
		}
		if (label === 'css' || label === 'scss' || label === 'less') {
			return new cssWorker()
		}
		if (label === 'html' || label === 'handlebars' || label === 'razor') {
			return new htmlWorker()
		}
		if (['typescript', 'javascript'].includes(label)) {
			return new tsWorker()
		}
		return new EditorWorker()
	}
}


const config = ref({
	zip: null,
	compCode: '',
	compVersion: '',
	decorations: [],
	session: null,
	languages: null,
	fileMenu: [],
	defaultProps: {
		children: 'children',
		key: 'label',
		title: 'label'
	}
})
const reNameDiag = ref({
	visiable: false,
	name: ""
})
const compFileEditor = ref();
let monacoInstance = null;
const props = defineProps(['compCode', 'compVersion', 'readonly', 'connected'])

// 在树节点加上点击事件
const handleNodeClick = (selectedKeys, {node: {dataRef}}) => {
	const data = dataRef;
	if (!data.dir) {
		config.value.currentPath = data.path
		// 将点击的文件内容显示在编译框内
		config.value.zip
			.file(data.path)
			.async('string')
			.then((res) => {
				// toRaw(this.monacoInstance).setValue(res);
				var s = data.label
				console.log(res,s)
				/*只获取后缀*/
				var suffix = s.match(/[^.]+$/)[0] //txt
				if (!suffix) {
					suffix = 'txt'
				}
				//重新转换一下
				if (suffix == 'js') {
					suffix = 'javascript'
				}
				if (suffix == 'properties') {
					suffix = 'ini'
				}
				// 新建一个model
				const newModel = monaco.editor.createModel(res, suffix)
				// 设置编辑器的model为newModel
				monacoInstance.setModel(newModel)
				//monacoInstance.setValue(res);
				//target目录需要设置为只读
				if (data.path.startsWith('target')) {
					monacoInstance.updateOptions({readOnly: true})
				}

				// else {
				//   toRaw(this.monacoInstance).updateOptions({readOnly: false})
				// }
			})
	}
}
const renameFile = async (oldFilePath, newFilePath, dir) => {
	// 获取需要重命名的文件

	let prevFolderPath = findPrevFolderPath(oldFilePath);
	if (!prevFolderPath) {
		return false
	}

	let oldFileName = findLastFileNamePath(oldFilePath);


	let folder = config.value.zip.folder(prevFolderPath);


	let file = null;
	let text = null;
	if (!dir) {
		file = config.value.zip.file(oldFilePath);
		text = await file.async('string')
	} else {
		file = config.value.zip.folder(oldFilePath);
	}

// 如果文件存在，则重命名
	if (folder) {
		if (!dir) {
			// 删除旧文件
			folder.remove(oldFileName);
			// 添加新文件
			folder.file(newFilePath, text);
		} else {


			let newFolder = folder.folder(newFilePath);
			let writeFile = []
			file.forEach((relativePath, file) => {
				if (file.dir) {
					newFolder.folder(relativePath)
				} else {
					writeFile.push({
						relativePath: relativePath,
						file: file
					})
				}
			})
			if (writeFile) {
				for (let {relativePath, file} of writeFile) {
					let pf = findPrevFolderPath(relativePath);
					let folder = newFolder.folder(pf);
					let f = findLastFileNamePath(relativePath);
					let newText = await file.async('string')
					folder.file(f, newText)
				}
			}

			folder.remove(oldFileName);
		}

	}
	return true
}

const findPrevFolderPath = (path) => {
//	let file = config.value.zip.folder("source/square-lake-plugin-step-switchCaseTwo");

	if (!path) {
		return;
	}
	if (path.indexOf("/") == -1) {
		return;
	}

	let pathArray = path.split("/");
	let result = "";
	for (let i = 0; i < pathArray.length; i++) {
		let currentName = pathArray[i];

		if (i == pathArray.length - 1) {
			break;
		} else {
			result += (i == 0 ? "" : "/") + currentName;
		}
	}
	return result;

}

const findLastFileNamePath = (path) => {
//	let file = config.value.zip.folder("source/square-lake-plugin-step-switchCaseTwo");

	if (!path) {
		return;
	}
	if (path.indexOf("/") == -1) {
		return path;
	}

	let pathArray = path.split("/");
	let result = "";
	for (let i = 0; i < pathArray.length; i++) {
		let currentName = pathArray[i];

		if (i == pathArray.length - 1) {
			result = currentName;
			break;
		}
	}
	return result;

}

const findNodeInfo = (path, data) => {
	let result = null;
	if (!data || data.length <= 0) {
		return result;
	}
	for (let i = 0; i < data.length; i++) {
		let d = data[i];

		if (d.path == path) {
			result = {
				node: d,
				index: i,
				parent: data
			};
			break;
		}
		let childrenList = d.children;
		if (childrenList && childrenList.length > 0) {
			let tempNode = findNodeInfo(path, childrenList);
			if (tempNode) {
				result = tempNode;
				break;
			}
		}
	}
	return result;
}
const findNode = (path, data) => {
	let node = null;
	if (!data || data.length <= 0) {
		return node;
	}
	for (let d of data) {
		if (d.path == path) {
			node = d;
			break;
		}
		let childrenList = d.children;
		if (childrenList && childrenList.length > 0) {
			let tempNode = findNode(path, childrenList);
			if (tempNode) {
				node = tempNode;
				break;
			}
		}
	}
	return node;
}
const onContextMenuClick = (treeKey, menuKey, label, isLeaf, path, dir) => {
	if (path.indexOf("/") == -1) {
		message.info("不能操作根节点")
		return
	}
	//重命名
	if (menuKey == '3') {
		reNameDiag.value.name = label;
		//新增子文件
	} else if (menuKey == '2') {
		reNameDiag.value.name = '';
		//删除
	} else if (menuKey == '1') {
		reNameDiag.value.name = '';
		//删除
	} else if (menuKey == '5' || menuKey == '4') {
		let node = findNodeInfo(path, config.value.fileMenu)
		removeFile(path);
		delete node.parent[node.index];
		return;
	}
	reNameDiag.value.dir = dir;
	reNameDiag.value.path = path;
	reNameDiag.value.visiable = true;
	reNameDiag.value.type = menuKey;
}

const saveFileByType = () => {
	reNameDiag.value.visiable = false;

	if (reNameDiag.value.type == '3') {
		//对文件做重命名
		let node = findNode(reNameDiag.value.path, config.value.fileMenu)
		if (!node) {
			return
		}
		let ps = replacePath(node.path, reNameDiag.value.name);
		let bool = renameFile(reNameDiag.value.path, reNameDiag.value.name, reNameDiag.value.dir);
		if (!bool) {
			return;
		}

		node.path = ps;
		node.label = reNameDiag.value.name
		//这里还要zip处理
	} else if (reNameDiag.value.type == '2') {
		let node = findNode(reNameDiag.value.path, config.value.fileMenu)
		addFile(reNameDiag.value.path, reNameDiag.value.name)

		let newNode = {
			label: reNameDiag.value.name,
			isLeaf: true,
			dir: false,
			path: reNameDiag.value.path + "/" + reNameDiag.value.name
		}
		if (!node.children) {
			node.children = []
		}
		node.children.push(newNode)
		//新增子文件
	} else if (reNameDiag.value.type == '1') {
		let node = findNode(reNameDiag.value.path, config.value.fileMenu)
		addFolder(reNameDiag.value.path, reNameDiag.value.name)

		let newNode = {
			label: reNameDiag.value.name,
			isLeaf: false,
			dir: true,
			path: reNameDiag.value.path + "/" + reNameDiag.value.name
		}
		if (!node.children) {
			node.children = []
		}
		node.children.push(newNode)
	}


}

const removeFile = async (folderPath) => {

	let prevFolderPath = findPrevFolderPath(folderPath);
	if (!prevFolderPath) {
		return false
	}
	let oldFileName = findLastFileNamePath(folderPath);
	let folder = config.value.zip.folder(prevFolderPath);

// 如果文件存在，则重命名
	if (folder) {
		folder.remove(oldFileName);
	}
	return true
}

const addFolder = (folderPath, fileName) => {
	let folder = config.value.zip.folder(folderPath);
	if (folder) {
		// 添加新文件
		folder.folder(fileName);
	}
}
const addFile = (folderPath, fileName) => {
	let folder = config.value.zip.folder(folderPath);
	if (folder) {
		// 添加新文件
		folder.file(fileName, "");
	}
}

const replacePath = (path, name) => {
	if (!path) {
		return name;
	}
	if (path.indexOf("/") == -1) {
		return name;
	}

	let pathArray = path.split("/");
	let result = "";
	for (let i = 0; i < pathArray.length; i++) {
		let currentName = pathArray[i];

		if (i == pathArray.length - 1) {
			result += (i == 0 ? "" : "/") + name;
		} else {
			result += (i == 0 ? "" : "/") + currentName;
		}
	}
	return result;
}


// 保存编辑
const saveValue = () => {
	if (config.value.currentPath) {
		const newValue = monacoInstance.getValue()
		config.value.zip.file(config.value.currentPath, newValue)
	}
}

const startConnect = async () => {
	const sq = await processCompDebuggerApi.connect({})
	if (config.value.session) {
		config.value.session.close()
	}
	const url = '127.0.0.1:82'
	config.value.session = await SocketD.createClient(`sd:ws://${url}/?u=a&p=2`).open()
}

const disconnect = async () => {
	const sq = await processCompDebuggerApi.disconnect({})
	if (config.value.session) {
		config.value.session.close()
	}
}
// 打包
const getFile = async () => {
	let file = await config.value.zip.generateAsync({type: 'blob'})
	let compFile = await blobToBase64(file)
	return compFile;
}


const blobToBase64 = (blob) => {
	return new Promise((resolve, reject) => {
		const reader = new FileReader()
		reader.onloadend = () => {
			resolve(reader.result)
		}
		reader.onerror = reject
		reader.readAsDataURL(blob)
	})
}
const getMenuList = (zip) => {
	const menu = []
	// 循环当前文件夹目录
	zip.forEach((relativePath, file) => {
		const dir = relativePath.split('/')
		var d = dir[0]
		if (d == 'target') {
			return
		}
		// // 文件夹
		// if (dir.length === 2 && file.dir) {
		//   menu.push({
		//     label: dir[0],
		//     leaf: false,
		//     path: file.name,
		//   });
		// }

		const path = zip.root ? zip.root + dir[0] : dir[0]
		if (dir.length > 1) {
			let findFlag = false
			for (const m of menu) {
				if (m.path == path) {
					findFlag = true
				}
			}
			if (findFlag) {
				return
			}
			let f = dir[0];
			menu.push({
				label: f,
				isLeaf: false,
				dir: file.dir,
				path: path
			})

			return
		}

		// 文件
		if (!file.dir) {
			for (let c of dir) {
				menu.push({
					label: c,
					isLeaf: true,
					dir: file.dir,
					path: file.name
				})
			}
		}
	})
	return menu
}
// 加载子目录
const loadNode = (node) => {
	return new Promise(resolve => {
		if (node.dataRef.children) {
			resolve();
			return;
		}

		const child = getMenuList(config.value.zip.folder(node.dataRef.path))
		node.dataRef.children = child;
		config.value.fileMenu = [...config.value.fileMenu];
		resolve();
	})
	// 把点击的文件夹路径传入

}
const getBreakpoint = () => {
	let breakpointLines = []
	if (monacoInstance) {
		let decorations = monacoInstance.getModel().getAllDecorations()
		let ids = []
		for (let decoration of decorations) {
			if (decoration.options.linesDecorationsClassName === 'breakpoints-code') {
				breakpointLines.push(decoration.range.startLineNumber)
			}
		}
	}
}
const removeFakeBreakPoint = () => {
	let model = monacoInstance.getModel()
	config.value.decorations = model.deltaDecorations(config.value.decorations, [])
}
const hasBreakPoint = (line) => {
	let decorations = monacoInstance.getLineDecorations(line)
	for (let decoration of decorations) {
		if (decoration.options.linesDecorationsClassName === 'breakpoints-code') {
			return true
		}
	}
	return false
}
const addBreakPoint = async (line) => {
	let model = monacoInstance.getModel()
	if (!model) return
	let value = [
		{
			range: new monaco.Range(line, 1, line, 1),
			options: {
				isWholeLine: true,
				linesDecorationsClassName: 'breakpoints-code'
			}
		},
		{
			range: new monaco.Range(line, null, line, null),
			options: {
				isWholeLine: true,
				className: 'debug-line-color'
			}
		}
	]
	model.deltaDecorations([], value)
}
const removeBreakPoint = async (line) => {
	let model = monacoInstance.getModel()
	if (!model) return
	let decorations
	let ids = []
	if (line !== undefined) {
		decorations = monacoInstance.getLineDecorations(line)
	} else {
		decorations = monacoInstance.getModel().getAllDecorations()
	}
	for (let decoration of decorations) {
		if (decoration.options.className === 'debug-line-color') {
			ids.push(decoration.id)
		}
		if (decoration.options.linesDecorationsClassName === 'breakpoints-code') {
			ids.push(decoration.id)
		}
	}
	if (ids && ids.length) {
		model.deltaDecorations(ids, [])
	}
}
const addFakeBreakPoint = (line) => {
	let model = monacoInstance.getModel()
	if (hasBreakPoint(line)) return
	let value = {
		range: new monaco.Range(line, 1, line, 1),
		options: {
			isWholeLine: true,
			linesDecorationsClassName: 'breakpoints-fake-code'
		}
	}
	config.value.decorations = model.deltaDecorations(config.value.decorations, [value])
}

onMounted(async () => {
	await loadZip(props.compCode, props.compVersion, props.readonly, props.connected)
	if (props.connected) {
		await startConnect()
	}
})
const loadZip = async (compCode, compVersion, readonly) => {
	config.value.compCode = compCode
	config.value.compVersion = compVersion
	if (!monacoInstance) {
		monaco.languages.register({id: 'customjava'})
		monaco.languages.setMonarchTokensProvider('customjava', customTokenProvider)
		monaco.editor.defineTheme('customTheme', customTheme)
		monaco.languages.setLanguageConfiguration('customjava', customLanguageConfiguration(monaco))
		//customjava代码格式化问题，这里使用了groovyBeautify 他是npm里第三方格式化java代码的插件库
		monaco.languages.registerDocumentFormattingEditProvider('customjava', {
			provideDocumentFormattingEdits: function (model, options, token) {
				var formattedCode = groovyBeautify(model.getValue())
				return [
					{
						range: model.getFullModelRange(),
						text: formattedCode
					}
				]
			}
		})

		//customjava代码提示问题
		//这里使用了registerCompletionItemProvider
		config.value.languages = monaco.languages.registerCompletionItemProvider('customjava', {
			provideCompletionItems: () => {
				let suggestions = []
				suggestions.push({
					label: '提示信息',
					//插入文本的方式，具体可以console monaco.languages.CompletionItemKind
					kind: monaco.languages.CompletionItemKind,
					insertText: '插入的文本',
					documentation: '插入的详情'
				})
				return {
					suggestions: suggestions
				}
			}
		})

		monacoInstance = monaco.editor.create(compFileEditor.value, {
			value: `//请选择文件进行编辑`,
			language: 'customjava',
			contextmenu: true,
			fontSize: 16,
			theme: 'customTheme', // 主题
			folding: true, // 是否折叠
			inherit: true,
			minimap: {
				enabled: true
			},
			fontSize: 14,
			fontFamily: 'Menlo, Monaco, Consolas, "Courier New", monospace',
			lineHeight: 22,
			foldingHighlight: true, // 折叠等高线
			foldingStrategy: 'indentation', // 折叠方式  auto | indentation
			showFoldingControls: 'always', // 是否一直显示折叠 always | mouseover
			disableLayerHinting: true, // 等宽优化
			emptySelectionClipboard: false, // 空选择剪切板
			selectionClipboard: false, // 选择剪切板
			automaticLayout: true, // 自动布局
			codeLens: false, // 代码镜头
			scrollBeyondLastLine: false, // 滚动完最后一行后再滚动一屏幕
			colorDecorators: true, // 颜色装饰器
			accessibilitySupport: 'off', // 辅助功能支持  "auto" | "off" | "on"
			lineNumbers: 'on', // 行号 取值： "on" | "off" | "relative" | "interval" | function
			lineNumbersMinChars: 5, // 行号最小字符   number
			enableSplitViewResizing: false,
			readOnly: readonly || false //是否只读  取值 true | false
		})
		// 编辑监听
		monacoInstance.onDidChangeModelContent(() => {
			saveValue()
		})
		monacoInstance.onMouseMove((e) => {
			removeFakeBreakPoint()
			if (
				e.target.detail &&
				e.target.detail.offsetX &&
				e.target.detail.offsetX >= 0 &&
				e.target.detail.offsetX <= 40
			) {
				let line = e.target.position.lineNumber
				let lineContent = monacoInstance.getModel().getLineContent(line).trim()
				if (lineContent) {
					addFakeBreakPoint(line)
				}
			}
		})
		monacoInstance.onMouseDown((e) => {
			if (
				e.target.detail &&
				e.target.detail.offsetX &&
				e.target.detail.offsetX >= 0 &&
				e.target.detail.offsetX <= 40
			) {
				// alert(2)
				let line = e.target.position.lineNumber
				let lineContent = monacoInstance.getModel().getLineContent(line).trim()
				if (lineContent) {
					if (!hasBreakPoint(line)) {
						addBreakPoint(line)
					} else {
						removeBreakPoint(line)
					}
					getBreakpoint()
				}
			}
		})
	} else {
		monacoInstance.setValue('//请选择文件进行编辑')
	}

	config.value.zip = null
	config.value.fileMenu = []

	let data = await processCompApi.getFile(config.value.compCode, config.value.compVersion);
	data = data.data;
	config.value.zip = new JSZip()
	//解压
	config.value.zip.loadAsync(data).then((zip) => {
		// this.folderZhankai(zip);
		config.value.fileMenu = getMenuList(zip)
	})
}

defineExpose({
	disconnect,
	getFile
})
</script>

<style scoped lang="scss">


/* 强制显示 Monaco Editor 相关元素 */
:deep(.monaco-editor),
:deep(.monaco-editor *),
:deep(div[class*="monaco"]),
:deep(div[class*="monaco"] *) {
	display: flex !important;
	visibility: visible !important;
	opacity: 1 !important;
}

</style>
